package nachos.network;

import java.util.ArrayList;

import nachos.machine.*;
import nachos.threads.*;
import nachos.userprog.*;
import nachos.vm.*;
import nachos.network.*;

/**
 * A kernel with network support.
 * 
 */


public class NetKernel extends UserKernel {
	private PostOffice postOffice;
	public NachosProtocol protocol;
	    
	// dummy variables to make javac smarter
	private static NetProcess dummy1 = null;
	    
	private int dataLength = 25;
	
	public SynchList freePorts;
	private Socket writeSocket;
	private Socket readSocket;
	private int clientPort = 0;
	private int serverPort = 5;
	private int linkAddr = Machine.networkLink().getLinkAddress();
	
	private static int portLimit = 127;
	
	private Socket[] pendingConnRequests;
	private Lock pendingConnLock;    
	    
    /**
     * Allocate a new networking kernel.
     */
    public NetKernel() {
	super();
    }

    /**
     * Initialize this kernel.
     */
    public void initialize(String[] args) {
	super.initialize(args);

	postOffice = new PostOffice();
	protocol = new NachosProtocol();
	freePorts = new SynchList();
	for(int i = 0; i < portLimit; i++){
		freePorts.add(i);
	}
	pendingConnRequests = new Socket[portLimit];
	pendingConnLock = new Lock();
		
    }

    /**
     * Test the network. Create a server thread that listens for pings on port
     * 1 and sends replies. Then ping one or two hosts. Note that this test
     * assumes that the network is reliable (i.e. that the network's
     * reliability is 1.0).
     */
    public void selfTest() {
    	super.selfTest();
    	//writeSocket = protocol.createSocket(link, 0, link, 1);
    	//readSocket = protocol.createSocket(link, 1, link, 0);
		    	
    	KThread clientThread = new KThread(new Runnable() {
    			public void run() {writeToServer(5); }
	    	});

    	KThread clientThread2 = new KThread(new Runnable() {
			public void run() {writeToServer(5); }
    	});
	
    	KThread serverThread = new KThread(new Runnable() {
		
    		public void run() { read(); System.out.println("server done reading"); }
    	});

    //serverThread.fork(); 
    //	clientThread.fork();
     //	clientThread2.fork();
    //	serverThread.join();
    //	clientThread.join();
    //	clientThread2.join();
    	//now let's just reset the process id
   // 	UserProcess.nextProcessNumber = 0;
    	System.out.println("NetKernel self test exiting......");
   }

    
    private void ping1(int dstLink) {
	int srcLink = Machine.networkLink().getLinkAddress();
	
	System.out.println("PING " + dstLink + " from " + srcLink);

	long startTime = Machine.timer().getTime();
	
	MailMessage ping;

	try {
	    ping = new MailMessage(dstLink, 1,
				   Machine.networkLink().getLinkAddress(), 0,
				   new byte[0]);
	}
	catch (MalformedPacketException e) {
	    Lib.assertNotReached();
	    return;
	}

	postOffice.send(ping);

	MailMessage ack = postOffice.receive(0);
	
	long endTime = Machine.timer().getTime();

	System.out.println("time=" + (endTime-startTime) + " ticks");	
    }

    private void pingServer1() {
	while (true) {
	    MailMessage ping = postOffice.receive(1);

	    MailMessage ack;

	    try {
		ack = new MailMessage(ping.packet.srcLink, ping.srcPort,
				      ping.packet.dstLink, ping.dstPort,
				      ping.contents);
	    }
	    catch (MalformedPacketException e) {
		// should never happen...
		continue;
	    }

	    postOffice.send(ack);
	}	
    }
    
    private void writeToServer(int serverPort) {
     	NetProcess clientProcess = new NetProcess();
     	
    	Lib.debug('n', "Created client process");
    	int socket = clientProcess.handleSyscall(11, linkAddr, serverPort, 0, 0);
    	System.out.println("---------------CLIENT DONE: Client got descriptor " + socket);
    	
 //   	clientProcess.handleSyscall(8, socket, 0, 0, 0);
//    	Lib.debug('n',"Client starting to write");
//    	byte[] content = new byte[dataLength];
//    	System.out.println("Writing bytes: ");
//    	for(byte i = 0; i < content.length; i++){
//    		content[i] = i;
//    		System.out.print(i + ", ");
//    	}
//    	System.out.print('\n');
//    	clientProcess.handleSyscall(7, socket, content, a2, a3)
//    	writeSocket.write(content, 0, content.length);
//    	System.out.println("witing " + content.length + " bytes to "+ writeSocket.dstPort + " from " + writeSocket.srcPort);
//    	Lib.debug('n',"Client done writing");
    }

	
	private void read(){
		NetProcess serverProcess = new NetProcess();
		Lib.debug('n', "Created server process");
		int connections = 0;
		int socketNb = -1;
		do{
			socketNb = serverProcess.handleAccept(serverPort);
			if(socketNb > -1){
				System.out.println("server accepted connection, file descriptor: " + socketNb);
				connections++;
			}
			///ThreadedKernel.alarm.waitUntil(10000);
			KThread.currentThread().yield();
		}while(connections < 2);
		System.out.println("--------- DONE --- server done accepting connections....");
		
		//socket.open();
//		int bytesRead = 0;
//		byte[] buffer = new byte[dataLength];
//		
//		while (bytesRead < dataLength -20) {
//		    //NachosPacket ping = protocol.receive(1);
//			KThread.currentThread().yield();
//			bytesRead = bytesRead + readSocket.read(buffer, bytesRead, (dataLength -2) - bytesRead);
//		}
//		
//		// now read the remaining 2 bytes:
//		bytesRead = bytesRead + readSocket.read(buffer, bytesRead, 20);
//		
//		
//		System.out.println("Received data: ");  
//		for(byte i = 0; i < bytesRead; i++){
//			System.out.print(buffer[i]+ ", ");
//		}
//		System.out.print('\n');
//		Lib.debug('n',"server done reading");
     }
    
    /**
     * Start running user programs.
     */
    public void run() {
	super.run();
    }
    
    /**
     * Terminate this kernel. Never returns.
     */
    public void terminate() {
	super.terminate();
    }

   public void addToPending(Socket socket){
	   pendingConnLock.acquire();
	   Lib.debug('n',"Adding pending socket on port: " + socket.srcPort);
	   pendingConnRequests[socket.srcPort] = socket;
	   pendingConnLock.release();
   }
   /**
    * Returns a pending connection for this port or null if 
    * nothing is pending
    * @param socket
    * @return
    */
   public Socket getPending(int port){
	   Socket retVal = null;
	   pendingConnLock.acquire();
	   retVal = pendingConnRequests[port];
	   if(retVal != null)
		   Lib.debug('n', "removing pending socket on port: " + port);
	   pendingConnRequests[port] = null;
	   pendingConnLock.release();
	   return retVal;
   }
 
}
